package pl.edu.agh.io.fx3.treerebuilder;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import pl.edu.agh.io.fx3.exceptions.FileAlreadyExistsException;
import pl.edu.agh.io.fx3.exceptions.FileNotFoundException;
import pl.edu.agh.io.fx3.saver.dao.FileUtilsDAO;
import pl.edu.agh.io.fx3.treecreator.disk.DirectoryInfo;
import pl.edu.agh.io.fx3.treecreator.disk.DiskTreeConfig;
import pl.edu.agh.io.fx3.treecreator.disk.DiskTreeCreator;


/**
 * Class which is responsible for rebuilding folders' hierarchy when in its configuration file the number
 * of files or folders are changed between systems runtimes.
 * 
 * @author Jaroslaw Janik
 */
public class DiskTreeRebuilder {

	private FileUtilsDAO dao;
	private DiskTreeCreator diskTreeCreator;
	private DiskTreeConfig diskTreeConfig;

	/**
	 * Tree Rebuilder constructor which gets references to other modules to communicate.
	 * 
	 * @param dao Reference to instance of dao.
	 * @param diskTreeCreator Reference to instance of Disk Tree Creator.
	 * @param diskTreeConfig Reference to instance of Disk Tree Config.
	 */
	public DiskTreeRebuilder(FileUtilsDAO dao, DiskTreeCreator diskTreeCreator, DiskTreeConfig diskTreeConfig) {
		this.diskTreeCreator = diskTreeCreator;
		this.dao = dao;
		this.diskTreeConfig = diskTreeConfig;
	}
	
	/**
	 * The main function which rebuilts folders' hierarchy. It checks whether each folder does not contain
	 * files more than required number. If so, it copies files to empty spaces and updates database.
	 */
	public void rebuildTree() {
		File mainDir = new File(diskTreeConfig.getRootPath() + DiskTreeConfig.ROOT_DIR);
		
		rebuildingDFS(mainDir);
	}
	
	private void copyFile(File source, File dest) {
		InputStream in;
		OutputStream out;
		
		try {
			in = new FileInputStream(source);
			out = new FileOutputStream(dest);
			
			byte[] buf = new byte[1024];
			int len;
			
			while ((len = in.read(buf)) > 0) {
				out.write(buf, 0, len);
			}
			
			in.close();
			out.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	private void moveHierarchy(File dir) {
		FileFilter fileFilter = new FileFilter() {
		    public boolean accept(File file) {
		        return file.isDirectory();
		    }
		};
		
		File[] dirs = dir.listFiles(fileFilter);
		
		for (File directory : dirs) {
			moveHierarchy(directory);
			directory.delete();
		}
		
		fileFilter = new FileFilter() {
		    public boolean accept(File file) {
		        return file.isFile();
		    }
		};
		
		File[] files = dir.listFiles(fileFilter);
		
		for (File file : files) {
			try {
				String vfsPath = dao.getVirtualPath(file.getAbsolutePath());
				dao.deleteUsingVirtualPath(vfsPath);
				File newFile = diskTreeCreator.addFile(vfsPath);
				copyFile(file, newFile);
				file.delete();
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			} catch (FileAlreadyExistsException e) {
				e.printStackTrace();
			}
		}
	}
	
	private void rebuildingDFS(File dir) {
		FileFilter fileFilter = new FileFilter() {
		    public boolean accept(File file) {
		        return file.isDirectory();
		    }
		};
		
		File[] dirs = dir.listFiles(fileFilter);
		
		int dirsCount = dirs.length;
		
		int j = 1;
		for (int i = 0; i < dirsCount; ++i) {
			if (j <= dirsCount - diskTreeConfig.getMaxFolders()) {
				Map<String, DirectoryInfo> notFullDirs = diskTreeCreator.getNotFullDirs();
				List<String> paths = new LinkedList<String>();
				
				for (String path : notFullDirs.keySet()) {
					if (path.startsWith(dirs[i].getAbsolutePath())) {
						paths.add(path);
					}
				}
				
				for (String path : paths) {
					notFullDirs.remove(path);
				}
				
				moveHierarchy(dirs[i]);
				dirs[i].delete();
				++j;
			}
			else
			{
				rebuildingDFS(dirs[i]);
			}
		}
		
		fileFilter = new FileFilter() {
		    public boolean accept(File file) {
		        return file.isFile();
		    }
		};
		
		File[] files = dir.listFiles(fileFilter);
		
		int filesCount = files.length;
		
		j = 1;
		for (int i = 0; i < filesCount; ++i) {
			if (j <= filesCount - diskTreeConfig.getMaxFiles()) {
				try {
					String vfsPath = dao.getVirtualPath(files[i].getAbsolutePath());
					dao.deleteUsingVirtualPath(vfsPath);
					File newFile = diskTreeCreator.addFile(vfsPath);
					copyFile(files[i], newFile);
					files[i].delete();
					++j;
				} catch (FileNotFoundException e) {
					e.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
				} catch (FileAlreadyExistsException e) {
					e.printStackTrace();
				}
			}
		}
	}
}
